Skip to content

release: Bazarr+ v2.6.2 (Clockwork) - #430

Merged
LavX merged 69 commits into
masterfrom
development
Sep 7, 2026
Merged

release: Bazarr+ v2.6.2 (Clockwork)#430
LavX merged 69 commits into
masterfrom
development

Conversation

@LavX

@LavX LavX commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Release Bazarr+ v2.6.2 (Clockwork) from development. This stability patch includes immediate upload visibility with background sync, safe subtitle publication and ownership, recoverable partial translations, model routing and settings fixes, provider error handling, and dependency updates. See the archived release notes for the full inventory and upgrade notes.

Development head 83b59cf contains the reviewed corrections from #429 and exactly matches the user-approved test candidate. The proposed merge preserves all 1,436 candidate file/mode/gitlink entries and adds only the unchanged Sponsors configuration already on master. Both updated site files match the tested candidate; existing published assets are retained.

Validation:

  • Full local CI passed with 944 frontend tests, 3,914 backend tests on each of Python 3.12/3.13/3.14, and native PostgreSQL coverage. Node 24.20.0, type checks, lint, formatting and the production build passed.
  • The exact production image passed 192 isolated runtime cases, 49 default-start checks and 517 deployed-file comparisons. The maintainer approved the running test candidate.
  • Browser checks passed ten translator scenarios, six upload scenarios, and 18 release slides and destinations. The first tour run reported two HTTP callback errors; a diagnostic-only repeat passed without guard or application changes. The cause remains unconfirmed and both results are retained.
  • Full configuration, 63 provider configurations/versions, library/history counts, launch settings, the separate translator and rollback containers were preserved.
  • Fresh development CI and release-PR CI each passed all four jobs on 83b59cf. CodeQL passed Python, JavaScript/TypeScript and Actions analysis. Independent verification confirmed the exact proposed merge tree.

Provider bundles use a separate release channel. Catalog promotion remains in LavX/bazarr-provider-catalog#113; updating the application alone does not install those bundles. Live provider verification limits are disclosed in the release notes.

This PR is prepared for release approval. Merging to master triggers image and site workflows. Creating and publishing v2.6.2 advances the version and latest image tags. Those publishing actions require the maintainer's final approval.

dependabot Bot and others added 30 commits September 3, 2026 17:55
Bumps [guessit](https://github.com/guessit-io/guessit) from 3.8.0 to 4.4.0.
- [Release notes](https://github.com/guessit-io/guessit/releases)
- [Changelog](https://github.com/guessit-io/guessit/blob/develop/CHANGELOG.md)
- [Commits](guessit-io/guessit@v3.8.0...v4.4.0)

---
updated-dependencies:
- dependency-name: guessit
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the mantine group in /frontend with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) | `9.5.2` | `9.6.0` |
| [@mantine/dropzone](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/dropzone) | `9.5.2` | `9.6.0` |
| [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `9.5.2` | `9.6.0` |
| [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `9.5.2` | `9.6.0` |
| [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `9.5.2` | `9.6.0` |
| [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `9.5.2` | `9.6.0` |


Updates `@mantine/core` from 9.5.2 to 9.6.0
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/core)

Updates `@mantine/dropzone` from 9.5.2 to 9.6.0
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/dropzone)

Updates `@mantine/form` from 9.5.2 to 9.6.0
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/form)

Updates `@mantine/hooks` from 9.5.2 to 9.6.0
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/hooks)

Updates `@mantine/modals` from 9.5.2 to 9.6.0
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/modals)

Updates `@mantine/notifications` from 9.5.2 to 9.6.0
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/notifications)

---
updated-dependencies:
- dependency-name: "@mantine/core"
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/dropzone"
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/form"
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/hooks"
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/modals"
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/notifications"
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.51 to 2.0.52.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

---
updated-dependencies:
- dependency-name: sqlalchemy
  dependency-version: 2.0.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Preserve the supplied Arr instance when the media row is unavailable, and skip ownerless history writes safely. Cover both history writers against the migrated ownership constraint.

Fixes #404
Resolve matching profile targets before evaluating the score threshold. Disabled or unrelated profiles return silently, while eligible low-score sources log one skip.

Fixes #405
Reuse instance badges for results whose media kind has multiple configured instances, while preserving local-ID navigation and title filtering.

Fixes #397
Fix history ownership when media lookup fails
Check translation eligibility before source-score logging
Show owning instance names in global search results
Preserve provider error categories across the worker boundary
…ished jobs

The AI Subtitle Translator sidecar finishes a job as "partial" when any
batch fails and returns every line, untranslated positions carrying the
source text. The poll loop treated that as a hard failure and returned
None without reading the result, so a 286-line translation with 57 of 58
batches translated was thrown away after the credits were spent. Partial
results are now saved like completed ones; the sidecar's error is logged,
shown as a notification and recorded on the History entry.

The same loop gave up after 30 minutes regardless of what the sidecar
reported. A 1175-line movie took 35 minutes with reasoning enabled and a
shrunk batch size; the sidecar completed it and Bazarr had already
reported a failure. The sidecar owns per-request timeouts and moves every
job to a terminal state itself, so polling now continues while it reports
queued or processing (12-hour safety net) and stops only when the status
endpoint has not answered for 10 minutes.

The destination of a translation was resolved with the existence-only
lookup get_external_subtitles_path, so with the absolute or relative
subtitle-folder setting a first-time translation got None and crashed on
save, for every translator; the mods tool built its output path the same
way. A dedicated destination helper keeps an existing file's precedence
and otherwise resolves and creates the configured folder like downloads.

OpenRouter retired the config default and the documented recommendation
(google/gemini-2.5-flash-preview-05-20 and
google/gemini-2.5-flash-lite-preview-09-2025) along with three ids in the
settings dropdown. The default is google/gemini-2.5-flash-lite now, a
startup migration rewrites the retired ids to their GA successors, and the
dropdown, README and guide are refreshed.
Review finding: the completed branch accepted an empty result list as a
finished translation, which would save every source line under the
target name and record a success in History. At least one line is
required, the same rule the partial branch already applies.
Update Browserslist to the patched 4.28.7 release
Align the cryptography security floor and verify stored secrets
Scope generated sync outputs to their owning media
fix(translator): keep partial sidecar results and stop timing out finished jobs
Compare complete installed versions using packaging with a lazy pip-vendored
fallback. Reject prereleases below final floors and wrong postrelease pins
while preserving installed-version, bootstrap and repair behavior.
fix: align startup requirements and compare complete versions
The submodule still referenced v1.1.0 while the published image and the
partial-result handling in development rely on the 1.3.1 sidecar: tolerant
parsing of model replies, a batch size that recovers, and honest partial
counts.
LavX and others added 27 commits September 6, 2026 09:13
The AI Subtitle Translator sorts OpenRouter providers by throughput unless it
is told otherwise, and Bazarr never told it anything: the request config
carried the key, model, temperature, concurrency, batch count and reasoning,
but no provider block. Every job therefore went to the fastest provider,
which is often not the cheapest, and a user who typed model:floor into the
model field got a slug asking for the cheapest provider next to a body asking
for the fastest one.

A new translator.openrouter_provider_routing setting (default throughput,
which keeps the wire shape every existing install has today) is sent as
config.provider.sort by both request builders, the async translate job and
the manual content endpoint, through one shared helper. The Provider Routing
selector in the model tuning card offers fastest, fastest with the priority
tier (:nitro), cheapest, cheapest with the flex tier (:floor), lowest
latency, and OpenRouter's own load balancing. The guide explains that the
provider decides what a request costs; the two shortcut options need AI
Subtitle Translator 1.3.4 or newer.
Merge the reviewed and verified change into development.
Merge the reviewed and verified change into development.
Merge the reviewed and verified change into development.
Merge the reviewed and verified change into development.
Merge the reviewed and verified upload dialog correction into development.
…or and default

An AI Subtitle Translator before 1.3.4 forwards provider.sort to OpenRouter
verbatim, and OpenRouter rejects nitro, floor and default there, so a user who
picked one of them against an older sidecar (the pinned submodule included)
would have had every translation fail.

Bazarr now reads the sidecar's version from its health endpoint once per URL
(cached for five minutes) and, below 1.3.4 or when the version cannot be read,
sends the plain sort each value stands for: throughput for nitro and default,
price for floor, with a warning in the log. Sidecars from 1.3.4 on receive the
values unchanged. The guide says so.
…ing setting

The guide tells users they can append the shortcut to the model id, but the
sort Bazarr sent came from the selector alone. A model ending in :floor next
to the default Fastest setting therefore asked OpenRouter for the cheapest
provider in the slug and the fastest one in the body, which is exactly the
conflict this feature exists to remove, and an older sidecar forwards both.

A routing suffix on the model id now decides the sort: price for :floor,
throughput for :nitro, whatever the selector says. Other variants such as
:thinking are left alone. Sidecars from 1.3.4 on drop the sort for a typed
shortcut anyway, so this only matters to older ones, where it is the
difference between agreeing with the slug and contradicting it.
v1.3.4 adds the provider routing values behind the Provider Routing setting:
price, latency, nitro, floor and default alongside throughput, with nitro and
floor going out as OpenRouter's slug shortcuts and an unknown value rejected
at validation instead of travelling to OpenRouter as an invalid sort. A
routing shortcut typed into the model id is honoured instead of competing
with the throughput default, and the reasoning and metadata lookups strip it
so a suffixed slug still resolves.

Supersedes the v1.3.3 bump this branch carried; v1.3.3 is included in v1.3.4.
chore(sidecar): bump ai-subtitle-translator to v1.3.4
feat(translator): provider routing setting for OpenRouter (fastest, cheapest, :nitro, :floor)
OpenRouter's :nitro and :floor are ways to route to a model, not models. Typing
one into the AI Model field left the Provider Routing selector showing something
else, and the selector then did nothing, so the page could read "Cheapest" while
every request asked for the fastest and pricier provider. The suffixed id also
never matched OpenRouter's model list, so the details card lost the cost
estimate, the context window and the pricing, and the guard that disables
Reasoning Mode for models without reasoning support never ran.

Typing a routing shortcut now moves it into the Provider Routing selector and
leaves the plain model id in the field, with a line saying so. One field owns
the routing, the page cannot contradict itself, and the details resolve again.
The details lookup also strips a routing suffix on its own, so a config saved
before this still shows its model card.

Every other variant, :free, :thinking, :online, :extended, :exacto and :batch
among them, names a different model rather than a way to reach the same one, and
is left on the id untouched.
Adoption ran on every keystroke, and mid-word the text can be an exact suffix
of something longer. Typing "some/model:floorplan" therefore adopted :floor the
moment the "r" landed, rewrote the field to "some/model", and appended the rest
to produce "some/modelplan". The documented typo ":floorr" and lookalikes such
as ":nitrox" corrupted the same way; the pure-function tests covered them, but
the component tests pasted the value in one go and never typed through the
suffix.

Adoption now runs when the field is left, so a shortcut is only recognised once
the user has finished typing it. Saving without leaving the field is still
correct: the backend already gives a typed suffix precedence over the selector.
Four problems, all from adopting the shortcut at the wrong moment or only
halfway.

A keyboard save never triggered it. Ctrl+S submits the staged values straight
away, so typing a shortcut and saving without leaving the field stored the
suffixed model next to the old routing, and the settings contradicted each
other after a reload. The shortcut now blurs the focused field and waits a tick
for that change to land before submitting, which fixes every settings input
that commits on blur rather than on change.

A stacked id kept one shortcut. "model:nitro:floor" dropped only ":floor" and
saved "model:nitro" with the selector on floor, and both the backend and the
sidecar then read ":nitro" off the id and let it win. All routing shortcuts now
come off and the last one typed is the one adopted.

A padded id was trimmed for the lookup but not for the setting, so the details
card showed a valid model while the request would have carried the padding on
the slug. The normalised id is written back whether or not a shortcut was found.

The notice claimed the routing was still what it adopted, even after the
selector had been changed by hand. It now follows the setting.
…Ctrl+S

Pressing Enter in a settings field submits the form natively, without blurring
it first. An input that stages its value on blur, such as the AI Model field
adopting an OpenRouter routing shortcut, therefore lost the user's last edit:
the raw suffixed model was saved next to the previous routing, and the two
contradicted each other after a reload. The keyboard shortcut had the same
problem and was fixed on its own, which left the more common path open.

Blurring and the deferred submit now sit in one place that the form's onSubmit
and the shortcut both call, so the Save button, Enter and Ctrl+S behave the
same.
feat(translator): adopt a routing shortcut typed into the model id
The manual translate action on a movie produced a job called "Translating EN
to HU" with no film in it, and the AI Translator jobs table showed only the
language pair. Episodes were fine.

get_title and get_description branch on media_type, and the callers spell it
two different ways: the translate endpoint sends "movie" while the batch and
mass-operation paths send "movies". Only the plural was matched, so a movie
fell through to the series branch, found no sonarr_series_id and returned an
empty string. That emptied the job label, the Media column, and the overview
the AI translator puts in its prompt, which quietly cost movies their context.

Both functions now accept either spelling through one predicate, so a caller
cannot lose the title again by picking the other word.
…ance

radarrId and sonarrSeriesId are unique only together with arr_instance_id, so
looking a title up by the upstream id alone can return a sibling instance's
media. With two Radarr instances holding the same id, a translation could be
labelled with the wrong film and, through get_description, hand the wrong
overview to the provider as prompt context.

Both helpers now take the owning instance and narrow every query with it. The
owner is threaded from translate_subtitles_file, which already had it, through
the factory into the translator services, so the label and the prompt describe
the media the user actually asked about. Callers with no owner pass None and
behave as before.

The movie branch was unreachable from the manual endpoint until the media_type
spelling fix in this branch, so this closes the hole that fix would otherwise
have opened.
fix(translate): name the media in translation job labels for movies
…-262

fix(upload): show saved subtitles before background synchronization
Preserve later subtitle edits and deletions while background sync runs.
Refresh media services after outputs are saved and keep media ownership
checks when retiring generated subtitles. Report cleanup errors without
losing track of a successfully saved subtitle.

Stop Gemini requests and retries after cancellation and clear progress.

Follow up on #428.
Render all four cards for zero counts, loading and connection errors.
Show a dash for unavailable counts and preserve valid counts when only
the jobs list fails.
Prepare the stability patch with subtitle publication, translation,
provider handling, routing, upload and dependency corrections.

- Stamp package_info with version 2.6.2.
- Add 18 in-app release slides and version persistence.
- Update the site version and roadmap and archive the release notes.

Deploy-verified: the test instance reports 2.6.2, starts cleanly and passes
all 18 release-tour slides, ten translator and six upload browser cases.
Add #429 to the complete release inventory.
Reuse a lazy ownership index within each library scan while keeping mutation checks fresh. Record successful sync promotion even when index refresh fails, and notify media consumers before sync setup can fail after an upload.

Cover scan ownership scaling and both publication failure paths with regression tests.
Include the reviewed subtitle sync corrections, Gemini cancellation handling, always-visible translator status cards and release preparation. The complete candidate passed local CI and test-instance verification and was approved by the maintainer.
@LavX
LavX marked this pull request as ready for review September 7, 2026 14:07
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T14:16:40.070698Z 83b59cf Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@LavX
LavX merged commit 6c354d6 into master Sep 7, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant